//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <exec/elf.h>
#include <mem/util.h>
#include <mem/heap.h>
#include <mem/page.h>
#include <kernio/io.h>
#include <multiboot.h>
#include <process/common.h>

// Defines

// debugging mode
#define DEBUG 0

// Types

// a kernel module in memory
struct kernel_module
{
	// the previous module
	struct kernel_module* prev;

	// virtual address range
	uint32_t basevirt;
	uint32_t topvirt;

	// name
	char modname[512];

	// valid module - 0 = no, 1 = yes
	// generally all should be valid
	uint32_t valid;

	// the next module
	struct kernel_module* next;
} __attribute__((packed));

// Variables

// kernel module load area start (all kernel modules will be loaded relative
// to this address)
#define MODULE_LOAD_ADDR 0xA0000000

// the kernel module list
struct kernel_module* kmodlist = 0;

// the kernel's symbol table (header)
struct Elf32_Shdr* kern_symtab_shdr = 0;

// the kernel's symbol table (symbol definitions)
struct Elf32_Sym* kern_symtab = 0;

// the kernel's string table
uint8_t* kern_strtab = 0;

// this symbol sits at the end of the kernel
extern char __end;

// Functions

// returns a symbol from a specific address within the kernel
// although, it doesn't actually return anything O_O
char* getkernsym( uint32_t addr )
{
	// is it even in the kernel?
	if( addr < 0x100000 || addr > ((uint32_t) &__end) )
	{
		dprintf( "<outside_kernel %x>", addr );
		return 0;
	}

	if(kern_symtab_shdr == 0)
	{
		dprintf("<unknown_kernel_symtab_not_avail>");
		return 0;
	}

	// go through the kernel symbol table, and try to find the relevant indexes
	size_t i;
	for( i = 0; i < (kern_symtab_shdr->sh_size / sizeof( struct Elf32_Sym )); i++ )
	{
		// verify that this symbol is safe
		if( kern_symtab[i].st_name == 0 )
			continue; // no name for this symbol

		// should we skip over checking
		if( ELF32_ST_TYPE( kern_symtab[i].st_info ) != STT_FUNC
		&& ELF32_ST_TYPE( kern_symtab[i].st_info ) != STT_NOTYPE
		&& ELF32_ST_TYPE( kern_symtab[i].st_info ) != STT_OBJECT )
			continue;

		// get the symbol name
		char* ext_symname = (char*) (kern_strtab + kern_symtab[i].st_name);

		// check for the same string, and a valid value
		uint32_t symsize = kern_symtab[i].st_size;
		if( symsize == 0 ) symsize = 1;
		if( (addr >= kern_symtab[i].st_value) && (addr < (kern_symtab[i].st_value + symsize)) )
		{
			// we found it!
			dprintf( "<%s+0x%x>", ext_symname, addr - kern_symtab[i].st_value );
			return 0;
		}
	}
	dprintf( "<0x100000+0x%x>", addr - 0x100000 );
	return 0;
}

// loads the kernel symbol table ready for use - also makes sure it cannot
// be modified?
void InitKernelSymbolTable( multiboot_info_t* mboot_ptr )
{
	// get the section header table address
	uint32_t sec_addr = mboot_ptr->u.elf_sec.addr;
	kSetPages(sec_addr, sec_addr + (mboot_ptr->u.elf_sec.size * mboot_ptr->u.elf_sec.num), 1);

	// get the section header table now
	struct Elf32_Shdr* shdrs = (struct Elf32_Shdr*) sec_addr;
	size_t i;
	for(i = 0; i < mboot_ptr->u.elf_sec.num; i++)
	{
		// what type is it?
		if(shdrs[i].sh_type == SHT_SYMTAB)
		{
			// make this unallocateable
			kSetPages(shdrs[i].sh_addr, shdrs[i].sh_addr + shdrs[i].sh_size, 1);

			// setup the globals
			kern_symtab_shdr = (struct Elf32_Shdr*) &shdrs[i];
			kern_symtab = (struct Elf32_Sym*) shdrs[i].sh_addr;
			if(kern_symtab_shdr)
			{
				// grab the string table
				kern_strtab = (uint8_t*) (shdrs[kern_symtab_shdr->sh_link].sh_addr);

				// and then make sure the string table can't be allocated
				kSetPages(shdrs[kern_symtab_shdr->sh_link].sh_addr, shdrs[kern_symtab_shdr->sh_link].sh_addr + shdrs[kern_symtab_shdr->sh_link].sh_size, 1);
			}
			else
			{
				kern_strtab = 0;
			}
			break;
		}
	}
}

// loads the kernel image for use in resolving symbols
void InitElfSystems( multiboot_info_t* mboot_ptr )
{
	// setup the kernel module list
	kmodlist = (struct kernel_module*) malloc(sizeof(struct kernel_module));
	if( !kmodlist ) { return; };
	kmodlist->prev = kmodlist->next = 0;
	kmodlist->valid = 1;
	kmodlist->basevirt = kmodlist->topvirt = MODULE_LOAD_ADDR;

	// the kernel header
	struct Elf32_Ehdr* khdr = 0;

	// the base address
	uint32_t filebase = 0;

	// module list
	module_t* mods = (module_t*) mboot_ptr->mods_addr;

	// grab the module list
	size_t z;
	for( z = 0; z < mboot_ptr->mods_count; z++ )
	{
		// grab the string
		//const char* modstr = (const char*) mods[z].string;

		// map the module in
		uint32_t page;
		for( page = (mods[z].mod_start & ~0xFFF); page <= (mods[z].mod_end & ~0xFFF); page += 0x1000 )
		{
			if(!IsMapped(0xfffff000, page))
			{
				MapPage(0xfffff000, page, page, PF_PRESENT | /*PF_READWRITE | writing is BAD */ PF_KERNELPAGE | PF_IMMUTABLE);
				kIncrRefCount(page);
			}
		}

		// find out the length
		/*size_t slen = strlen( modstr );

		// add enough bytes to it that we only get the kernel.bin section
		modstr += (slen - strlen( "kernel.bin" ));

		// is it the kernel?
		if( strcmp( "kernel.bin", modstr ) == 0 && khdr == 0 )
		{
			khdr = (struct Elf32_Ehdr*) mods[z].mod_start;
			filebase = mods[z].mod_start;
		}*/
	}

	return;

	// check that we did get a valid header
	if( !khdr || !filebase )
	{
		// no kernel module available
		return;
	}

	// grab the section headers, and find the symbol table
	struct Elf32_Shdr* tmp_kern_shdrs = (struct Elf32_Shdr*) (filebase + khdr->e_shoff);

	// find the symbol table section
	size_t i;
	for( i = 0; i < khdr->e_shnum; i++ )
	{
		// symbol table?
		if( tmp_kern_shdrs[i].sh_type == SHT_SYMTAB )
		{
			// setup the globals
			kern_symtab_shdr = (struct Elf32_Shdr*) (filebase + khdr->e_shoff + (khdr->e_shentsize * i));
			kern_symtab = (struct Elf32_Sym*) (filebase + tmp_kern_shdrs[i].sh_offset);
			if(kern_symtab_shdr)
				kern_strtab = (uint8_t*) (filebase + tmp_kern_shdrs[kern_symtab_shdr->sh_link].sh_offset);
			else
			{
				dprintf("no string table!\n");
				kern_strtab = 0;
			}
			break;
		}
	}
}

// loads an elf binary into the current address space
int32_t elf_load( const char* filename, uint32_t* entry_point )
{
	// open the file
	int fd = sys_open(filename, 0);
	if(fd < 0)
		return -1;

	// seek to the start of the file
	sys_seek( fd, 0, SEEK_SET );

    // read in the header
    char* header_buff = (char*) malloc( sizeof( struct Elf32_Ehdr ) );
    if( !header_buff ) return -1;
    int numbytes = sys_read( fd, header_buff, sizeof( struct Elf32_Ehdr ) );
    if( numbytes == -1 )
	{
		free( header_buff );
		return -1;
	}
    struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) header_buff;

    // verify that we should load this binary
    if( header->e_phnum > 64 )
    {
#if DEBUG
    	dprintf( "[LoadStaticBinaryFromFile] Attempt to load an insane number of program headers. Go away.\n" );
#endif
    	free( header_buff );
    	return -1;
    }

    // look for the program headers
    if( header->e_phnum > 0 )
    {
		// read in the program headers
		char* phdr = (char*) malloc( sizeof(struct Elf32_Phdr) * header->e_phnum );
		sys_seek( fd, header->e_phoff, SEEK_SET );
		sys_read( fd, phdr, sizeof(struct Elf32_Phdr) * header->e_phnum );

		// grab a pointer to the program headers
		struct Elf32_Phdr* prghdrs = (struct Elf32_Phdr*) phdr; // (elf_file + (header->e_phoff - sizeof(struct Elf32_Ehdr)));

        // go through each one, loading and mapping
        size_t i;
        for( i = 0; i < header->e_phnum; i++ )
        {
            // we can only load loadable sections
            if( prghdrs[i].p_type == PT_LOAD )
            {
                // get some information about the section
				uint32_t pagestart = (prghdrs[i].p_vaddr) & ~0xFFF;
				uint32_t pageend = (prghdrs[i].p_vaddr + prghdrs[i].p_memsz) & ~0xFFF;
                uint32_t pagecount = ((pageend - pagestart) / 0x1000) + 1;

                // figure out paging flags
                uint32_t pageflags = PF_PRESENT | PF_USER;
                if( prghdrs[i].p_flags & PF_X )
                    pageflags |= 0; // already setup correctly
                if( prghdrs[i].p_flags & PF_R )
                    pageflags |= 0; // already readable
                if( prghdrs[i].p_flags & PF_W )
                    pageflags |= PF_READWRITE; // writable

				// map some space for the section
				uint32_t space = prghdrs[i].p_vaddr & ~0xFFF;
				uint32_t z;
				for(z = 0; z < pagecount; z++)
				{
					if(!IsMapped(0xfffff000, space + (z * 0x1000)))
						kAllocateAndMap(1, space + (z * 0x1000), PF_PRESENT | PF_READWRITE | PF_USER);
				}

				// add a new region to the memory map
				if(currprocess->memmap == 0)
				{
					currprocess->memmap = (struct ctx_mem_map*) malloc(sizeof(struct ctx_mem_map));
					currprocess->memmap->next = 0;
				}
				else
				{
					struct ctx_mem_map* new_region = (struct ctx_mem_map*) malloc(sizeof(struct ctx_mem_map));
					new_region->next = currprocess->memmap;
					currprocess->memmap = new_region;
				}
				currprocess->memmap->base = space;
				currprocess->memmap->type = MEMMAP_ELEM_BINMEM;
				currprocess->memmap->size = pagecount * 0x1000;
				
				// this is the most important part of the entire loading algorithm - without it we may copy to the wrong offset
                uint32_t addend = prghdrs[i].p_vaddr & 0xFFF;

				// read in this section
				sys_seek( fd, prghdrs[i].p_offset, SEEK_SET );
				sys_read( fd, (void*) (space + addend), prghdrs[i].p_filesz );

				// zero out up to the memory size, then copy the file data across
				//memcpy( (void*) (space + addend), elf_file + (prghdrs[i].p_offset - sizeof(struct Elf32_Ehdr)), prghdrs[i].p_filesz );
				memset( (void*) (space + addend + prghdrs[i].p_filesz), 0, prghdrs[i].p_memsz - prghdrs[i].p_filesz );
            }
        }

        // free the used memory
        //free( elf_file );
		free( phdr );
    }
    else
    {
		// write to the debug console
		dprintf("ELF: e_phnum = 0?\n");

        // free the header
        free( header_buff );

        // return an invalid pid
		return -1;
    }

	// set the entry point parameter
	*entry_point = header->e_entry;

	// free the header
	free( header_buff );

    // success!
	return 0;
}

// loads a static binary from a file
int32_t LoadStaticBinaryFromFile( const char* exename, uint32_t kerncr3, uint32_t taskflags, uint32_t newcr3_override, char** argv )
{
	// create a new CR3, if needed
	uint32_t newcr3;
	if( newcr3_override == 0 )
	{
		// allocate room for it
		newcr3 = (uint32_t) kAllocatePages( 1 );

		// copy the kernel data into this one
		CloneDirectory( kerncr3, newcr3 );
	}
	else
		newcr3 = newcr3_override; // newcr3_override is a physical address of a page directory to use

    // setup the task
	uint32_t newproc, childid;
	newproc = kCreateProcessFromEXE(exename, taskflags, newcr3, 10, &childid, argv);
	newproc = childid;

	// return that id
	return childid;
}

// loads a static elf binary
int32_t LoadStaticBinary( uint32_t kerncr3, uint32_t elfptr, uint32_t taskflags, uint32_t newcr3_override, char** argv )
{
    // grab a pointer to the binary header
    struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) elfptr;

    // create a new CR3, if needed
    uint32_t newcr3;
    if( newcr3_override == 0 )
    {
    	// allocate room for it
		newcr3 = (uint32_t) kAllocatePages( 1 );

		// copy the kernel data into this one
		CloneDirectory( kerncr3, newcr3 );
    }
    else
    	newcr3 = newcr3_override; // newcr3_override is a physical address of a page directory to use

    // the new process id
    int32_t newproc = -1;

    // verify that we should load this binary
    if( header->e_phnum > 64 )
    {
#if DEBUG
    	dprintf( "[LoadStaticBinary] Attempt to load an insane number of program headers. Go away.\n" );
#endif
    	return -1;
    }

    // look for the program headers
    if( header->e_phnum > 0 )
    {
        // grab them
        struct Elf32_Phdr* prghdrs = (struct Elf32_Phdr*) (elfptr + header->e_phoff);

        // go through each one, loading and mapping
        size_t i;
        for( i = 0; i < header->e_phnum; i++ )
        {
            // we can only load loadable sections
            if( prghdrs[i].p_type == PT_LOAD )
            {
                // allocate enough space for this section (we DO NOT work with the already loaded binary)
                uint32_t pagecount = (prghdrs[i].p_memsz / 0x1000) + 1;

                // figure out paging flags
                uint32_t pageflags = PF_PRESENT | PF_USER;
                if( prghdrs[i].p_flags & PF_X )
                    pageflags |= 0; // already setup correctly
                if( prghdrs[i].p_flags & PF_R )
                    pageflags |= 0; // already readable
                if( prghdrs[i].p_flags & PF_W )
                    pageflags |= PF_READWRITE; // writable

                // map this into memory
                size_t z;
                for( z = 0; z < pagecount; z++ )
                {
					uint32_t* secptr = (uint32_t*) kAllocatePages( 1 /*pagecount*/ );
                    MapPage( newcr3, prghdrs[i].p_vaddr + (0x1000 * z), ((uint32_t) secptr) /*+ (0x1000 * z)*/, pageflags );
                    MapPage( kerncr3, prghdrs[i].p_vaddr + (0x1000 * z), ((uint32_t) secptr) /*+ (0x1000 * z)*/, pageflags );
                }

                // copy the data across
                memset( (void*) prghdrs[i].p_vaddr, 0, prghdrs[i].p_memsz );
                memcpy( (void*) prghdrs[i].p_vaddr, (void*) (elfptr + prghdrs[i].p_offset), prghdrs[i].p_filesz );
            }
        }

        // setup the task
        newproc = kCreateProcess( header->e_entry, taskflags, newcr3, 10, 0, argv );
    }
    else
		return -1;

    // erorr?
    if( newproc == -1 )
		return -1;
    else
		return 0;
}

// gets a symbol from an object file with a specific name
uint32_t elf_GetSymbolAddr( uint32_t filebase, const char* symname )
{
	// grab the header
	struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) filebase;

	// grab the section headers
	struct Elf32_Shdr* shdrs = (struct Elf32_Shdr*) (filebase + header->e_shoff);

	// look for the symbol table and the string table
	struct Elf32_Shdr* symtab_sh = 0;
	struct Elf32_Sym* symtab = 0;
	uint8_t* strtab = 0;
	size_t i;
	for( i = 0; i < header->e_shnum; i++ )
	{
		// is this entry a symbol table?
		if( shdrs[i].sh_type == SHT_SYMTAB )
		{
			// set all the relevant data
			symtab_sh = (struct Elf32_Shdr*) &shdrs[i];
			symtab = (struct Elf32_Sym*) (filebase + symtab_sh->sh_offset);
			strtab = (uint8_t*) (filebase + shdrs[symtab_sh->sh_link].sh_offset);
			break;
		}
	}

	// check that we have valid data
	if( !symtab_sh || !symtab || !strtab )
	{
#if DEBUG
		dprintf( "[elf_GetSymbolAddr] Couldn't get either the symbol table section header, the symbol table, or the string table.\n" );
#endif
		return 0;
	}

	// go through the kernel symbol table looking for the symbol
	for( i = 0; i < (symtab_sh->sh_size / sizeof( struct Elf32_Sym )); i++ )
	{
		// verify that this symbol is safe
		if( symtab[i].st_name == 0 )
			continue; // no name for this symbol

		// grab the symbol name for this symbol
		char* ext_symname = (char*) (strtab + symtab[i].st_name);

		// check for the same string, and a valid value
		if( strcmp( symname, ext_symname ) == 0 && symtab[i].st_value != 0 )
		{
			// return the value (this may actually be an offset, or a virtual address, or some other thing)
			return symtab[i].st_value;
		}
	}

	// none found, return 0 (which'll page fault if we try it)
	return 0;
}

// gets a symbol from either the object file or resolves to a kernel symbol
int32_t elf_GetSymbol( uint32_t filebase, uint32_t secid, uint32_t* sval, uint32_t symboltabid )
{
	// grab the symbol table
	struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) filebase;
	struct Elf32_Shdr* symtab_sect = (struct Elf32_Shdr*) (filebase + header->e_shoff + ( header->e_shentsize * symboltabid ) );

	// grab the right symbol
	struct Elf32_Sym* symboltab = (struct Elf32_Sym*) (filebase + symtab_sect->sh_offset);
	struct Elf32_Sym symbol = symboltab[secid];

	// grab the string for this symbol (for debugging purposes)
	struct Elf32_Shdr* strtab_sect = (struct Elf32_Shdr*) (filebase + header->e_shoff + ( header->e_shentsize * symtab_sect->sh_link ) );
	uint8_t* symname = (uint8_t*) (filebase + strtab_sect->sh_offset + symbol.st_name);

	// tell me it
#if DEBUG
	dprintf( "[elf_GetSymbol] Attempting to perform relocation on symbol '%s'\n", symname );
#endif

	// external symbol?
	if( symbol.st_shndx == 0 )
	{
		// tell me
#if DEBUG
		dprintf( "[elf_GetSymbol] %s is an external symbol\n", symname );
#endif

		// validate the kernel information
		if( !kern_symtab_shdr || !kern_symtab || !kern_strtab )
		{
#if DEBUG
			dprintf( "[elf_GetSymbol] No valid kernel tables loaded (missing one or more of symbol table header, symbol table, and string table)\n" );
#endif
			return -1;
		}

		// go through the kernel symbol table, and try to link the symbol
		size_t i;
		for( i = 0; i < (kern_symtab_shdr->sh_size / sizeof( struct Elf32_Sym )); i++ )
		{
			// verify that this symbol is safe
			if( kern_symtab[i].st_name == 0 )
				continue; // no name for this symbol

			// grab the symbol name for this symbol
			char* ext_symname = (char*) (kern_strtab + kern_symtab[i].st_name);

			// check for the same string, and a valid value
			//dprintf( "ext_symname = %s, symname = %s\n", ext_symname, symname );
			if( strncmp( ext_symname, (char*) symname, strlen( ext_symname ) ) == 0 && kern_symtab[i].st_value != 0 )
			{
				// these are all absolute values, so just set the pointer
				*sval = kern_symtab[i].st_value;

				return 0;
			}
		}

		// none found!
#if DEBUG
		dprintf( "[elf_GetSymbol] External reference '%s' not found!\n", symname );
#endif
		return -1;
	}
	else
	{
		// tell me
#if DEBUG
		dprintf( "[elf_GetSymbol] %s is an internal symbol\n", symname );
#endif

		// look for special relocations
		switch( symbol.st_shndx )
		{
			case SHN_ABS:
				*sval = symbol.st_value;
				return 0;
				break;
			case SHN_COMMON:
#if DEBUG
				dprintf( "[elf_GetSymbol] Common relocation found - all kernel modules *MUST* be comiled with '-fno-common'!\n" );
#endif
				return -1;
				break;
		}

		// grab a pointer to the section to modify
		struct Elf32_Shdr* sec = (struct Elf32_Shdr*) (filebase + header->e_shoff + (header->e_shentsize * symbol.st_shndx));

		// grab its address
		uint32_t symaddr = (uint32_t) (filebase + sec->sh_offset);
		*sval = symbol.st_value + symaddr;

		// success!
		return 0;
	}

	return -1;
}

// performs relocation
int32_t elf_RelocateElf( uint32_t filebase, struct Elf32_Rela* reloc, struct Elf32_Shdr section )
{
	// grab the address of the section in which we'll be working - sh_info for relocation sections
	// contains the section number of the section in which to perform relocation
	struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) filebase;
	struct Elf32_Shdr* sect = (struct Elf32_Shdr*) (filebase + header->e_shoff + ( header->e_shentsize * section.sh_info ) );

	// grab the linear address we can use to access this section's data
	uint32_t secdata_addr = (uint32_t) (filebase + sect->sh_offset);

	// grab the offset in which we need to perform this specific relocation
	uint32_t* where = (uint32_t*) (secdata_addr + reloc->r_offset);

	// grab the symbol
	uint32_t symbol_loc = 0; // the location of the symbol

	int32_t e = elf_GetSymbol( filebase, ELF32_R_SYM( reloc->r_info ), &symbol_loc, section.sh_link );
	if( e == -1 )
		return e;

	// what type of relocation do we need to perform?
	switch( ELF32_R_TYPE( reloc->r_info ) )
	{
		// absolute reference
		case R_386_32:
			// set it properly
			*where = symbol_loc + *where;
			break;
		// relative reference
		case R_386_PC32:
			// set it properly
			*where = symbol_loc + *where - ((uint32_t) where);
			break;
		default:
#if DEBUG
			dprintf( "[elf_RelocateElf] Unsupported relocation type!\n" );
#endif
			return -1;
	}

	// all done!
	return 0;
}

// loads a kernel module
int32_t LoadKernelModule( uint32_t UNUSED(kcr3), uint32_t filebase, const char* UNUSED(modnm) )
{
	// save the old cr3
	//uint32_t oldcr3 = currprocess->cr3;

	// grab the header for the file
	struct Elf32_Ehdr* header = (struct Elf32_Ehdr*) filebase;

	// section header data
	struct Elf32_Shdr* sections = 0;

	// relocation data (dynamically updated while relocating, must be the largest common type)
	struct Elf32_Rela* reloc = 0;

	// check the file's magic bytes
	if(	header->e_ident[EI_MAG0] != 0x7f
		||
		header->e_ident[EI_MAG1] != 0x45
		||
		header->e_ident[EI_MAG2] != 0x4c
		||
		header->e_ident[EI_MAG3] != 0x46
		)
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load an invalid kernel module [SIG_WRONG] %x!\n", header->e_ident[EI_MAG0] );
#endif
		return -1;
	}

	// verify that it's 32-bit
	if( header->e_ident[EI_CLASS] != ELFCLASS32 )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module for a non-32-bit target [CLS_WRONG]!\n" );
#endif
		return -1;
	}

	// check endianess
	if( header->e_ident[EI_DATA] != ELFDATALSB )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module with invalid endianess [END_WRONG]!\n" );
#endif
		return -1;
	}

	// verify the file version
	if( header->e_ident[EI_VERSION] != EV_CURRENT )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module with an invalid ELF version [VER_WRONG]!\n" );
#endif
		return -1;
	}

	// verify the target machine
	if( header->e_machine != EM_386 )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module that isn't i386! [MAC_WRONG]\n" );
#endif
		return -1;
	}

	// verify the file type (we only work with relocateables here)
	if( header->e_type != ET_REL )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module that isn't really a kernel module [TYP_WRONG]!\n" );
#endif
		return -1;
	}

	// verify the version (there are two version members)
	if( header->e_version != EV_CURRENT )
	{
		// failed!
#if DEBUG
		dprintf( "[LoadKernelModule] Attempted to load a kernel module that has an invalid ELF version [VE2_WRONG]!\n" );
#endif
		return -1;
	}

	// we're about to play with the linked lists, so we don't want to get
	// pre-empted
	uint32_t intstate;
	asm volatile( "pushf; pop %0; cli" : "=r" (intstate) );

	// find the end of the kernel module list
	struct kernel_module* tmp, *prev = 0;
	for( tmp = kmodlist; tmp != 0; prev = tmp, tmp = tmp->next );

	// valid space found?
	if( prev == 0 )
	{
		// restore interrupts, none found
		if( intstate & 0x200 )
			asm volatile( "sti" );
		return -1;
	}

	// get the load address to use
	uint32_t loadaddr = prev->topvirt;

	// allocate space for our own entry
	struct kernel_module* newent = (struct kernel_module*) malloc( sizeof( struct kernel_module ) );
	memset( newent, 0, sizeof( struct kernel_module ) );
	newent->basevirt = loadaddr;

	// grab a pointer to the section table
	sections = (struct Elf32_Shdr*) (filebase + header->e_shoff);

	// find out how many bytes we need to allocate for the binary to be loaded into higher memory
	// we do this here because the sections all have specific load points that they want to
	// be loaded to, and it needs to be done in another loop because there's no other way to get
	// the information safely
	uint32_t i;
	uint32_t total_binary_size = 0;
	for( i = 0; i < header->e_shnum; i++ )
	{
		// check that this section is program bits
		if( sections[i].sh_type == SHT_PROGBITS || sections[i].sh_type == SHT_NOBITS )
		{
			// verify that it is a valid section to load
			if(	( sections[i].sh_flags & SHF_ALLOC )
				||
				( sections[i].sh_flags & SHF_WRITE )
				||
				( sections[i].sh_flags & SHF_EXECINSTR )
				)
			{
				// add the size of this section
				total_binary_size += sections[i].sh_size;
			}
		}
	}

	// map the load address to this memory
	uint32_t allocspace = (uint32_t) kAllocateAndMap( (total_binary_size / 0x1000) + 1, loadaddr, PF_READWRITE | PF_PRESENT | PF_SUPERVISOR | PF_KERNELPAGE );
	
	// find all program sections and load them (also try to find an entry point)
	uint32_t entrypoint = 0;
	for( i = 0; i < header->e_shnum; i++ )
	{
		// check that this section is either program data or BSS
		if( sections[i].sh_type == SHT_PROGBITS || sections[i].sh_type == SHT_NOBITS )
		{
			// verify that it is a valid section to load
			if(	sections[i].sh_flags & SHF_ALLOC )
			{
				// get a pointer to the allocated space - use the mapping though
				uint32_t* ptr = (uint32_t*) (loadaddr);

				// if it's a nobits section, zero it out
				if( sections[i].sh_type == SHT_NOBITS )
					memset( ptr, 0, sections[i].sh_size );
				else // otherwise, copy it
					memcpy( ptr, (void*) (filebase + sections[i].sh_offset), sections[i].sh_size );

				// if this is an executable section, we need to setup the entry point to match
				if( sections[i].sh_flags & SHF_EXECINSTR )
					entrypoint = loadaddr;

				// update the section header information
				sections[i].sh_offset = loadaddr - filebase;

				// increment the load address so that the next section goes after this one
				loadaddr += sections[i].sh_size;
			}
		}
	}

	// perform relocation before we move the binary into its final location
	// this all occurs on the data at filebase (ie, where the file was loaded)
	for( i = 0; i < header->e_shnum; i++ )
	{
		// is it a relocation section?
		uint32_t relocsz;
		if( sections[i].sh_type == SHT_RELA )
			relocsz = sizeof( struct Elf32_Rela );
		else if( sections[i].sh_type == SHT_REL )
			relocsz = sizeof( struct Elf32_Rel );
		else
			continue;

		// perform the relocation
		uint32_t z;
		for( z = 0; z < (sections[i].sh_size / relocsz); z++ )
		{
			// grab a pointer to the relocation data
			reloc = (struct Elf32_Rela*) (filebase + (relocsz * z) + sections[i].sh_offset );

			// perform the relocation
			int32_t e = elf_RelocateElf( filebase, reloc, sections[i] );
			if( e == -1 )
			{
#if DEBUG
				dprintf( "[LoadKernelModule] Relocation failed!\n" );
#endif
				// free the used space
				kFreeMappedPages( (total_binary_size / 0x1000) + 1, allocspace );

				// restore interrupts
				if( intstate & 0x200 )
					asm volatile( "sti" );
				return -1;
			}
		}
	}

	// loadaddr now holds the top address
	newent->topvirt = (loadaddr & ~0xFFF) + 0x1000;

	// link it into the list
	newent->valid = 1;
	newent->prev = prev;
	newent->next = prev->next;
	if( prev->next )
		prev->next->prev = newent;
	prev->next = newent;

	// we need to run the entry point so that the loaded module can do what it needs
	uint32_t dmain_addr = elf_GetSymbolAddr( filebase, "dmain" );
	uint32_t* epoint = (uint32_t*) (entrypoint + dmain_addr);

	// restore interrupts
	if( intstate & 0x200 )
		asm volatile( "cli" );

	// call the entry point
	typedef void (*fp)();
	static fp dmain;
	dmain = (fp) epoint;
#if DEBUG
	dprintf( "dmain for %s at %x\n", modnm, dmain );
#endif
	dmain();
#if DEBUG
	dprintf( "dmain for %s returns\n", modnm );
#endif

	// success!
	return 0;
}

// loads an ELF binary (type: 1 = kmod, 0 = static) - a wrapper for the above two functions
int32_t LoadElf( uint32_t kcr3, uint32_t base, uint32_t type, uint32_t UNUSED(taskflags), uint32_t UNUSED(newcr3_override), const char* modnm, char** UNUSED(argv) )
{
	// what to do?
	if( type == 1 )
		return LoadKernelModule( kcr3, base, modnm );
	else if( type == 0 )
	{
		// load it up
		dprintf("OH NOES\n"); // :D
		//LoadStaticBinary( kcr3, base, taskflags, newcr3_override, argv );
	}
	return 0;
}
